﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using System.Diagnostics;
using System.Runtime.Serialization.Json;
using System.Xml;
using Dixon.Jscp;

namespace Dixon.JSClientPages
{
    /// <summary>
    /// Represents a stack frame within a JSClientPages error
    /// (not necessarily obtained from .NET)
    /// </summary>
    public class StackFrame : IScriptable
    {
        /// <summary>
        /// The file in which the error
        /// occurred
        /// </summary>
        [XmlElement("file")]
        public string File { get; set; }

        /// <summary>
        /// The line within the file where the error occurred
        /// </summary>
        [XmlElement("line")]
        public int Line { get; set; }

        /// <summary>
        /// The name of the method that caused the
        /// error
        /// </summary>
        [XmlElement("method")]
        public string Method { get; set; }

        #region IScriptable Members

        /// <summary>
        /// Outputs this page error as a JavaScript object, which
        /// can be used by the client-side JavaScript to display the error
        /// </summary>
        /// <param name="writer">a script writer</param>
        /// <returns></returns>
        public void WriteScript(ScriptWriter writer)
        {
            writer
                .StartObject()
                .WriteKeyValuePair("file", File)
                .EndItem()
                .WriteKeyValuePair("line", Line.ToString())
                .EndItem()
                .WriteKeyValuePair("method", Method.ToString())
                .EndObject();
        }

        #endregion
    }

    /// <summary>
    /// An enumeration of possible sources for page errors
    /// </summary>
    public enum PageErrorSource
    {
        /// <summary>
        /// Indicates that the error occurred in the compiler. This
        /// does not imply that the compiler is at fault.
        /// </summary>
        Compiler,

        /// <summary>
        /// Indicates that the error originated in the client page
        /// itself and, therefore, is in no way related to the compiler
        /// or its set-up.
        /// </summary>
        ClientPage
    }

    /// <summary>
    /// Represents an error that occurred on a JavaScript page.
    /// The server-side object is commonly used to convert .NET
    /// exceptions.
    /// </summary>        
    public class PageError : IScriptable
    {
        /// <summary>
        /// The name of the JavaScript method called to display
        /// this error
        /// </summary>
        const string DisplayMethod = "JSClientPageError.displayOnLoad";

        /// <summary>
        /// Namespace declarations (for XML-based serialization)
        /// </summary>
        [XmlNamespaceDeclarations]
        public XmlSerializerNamespaces xmlns = new XmlSerializerNamespaces();

        /// <summary>
        /// Indicates where the error originated (i.e. in the compiler
        /// or in the page itself)
        /// </summary>
        [XmlElement("source")]
        public PageErrorSource Source { get; set; }


        /// <summary>
        /// The error message, describing the nature of the error with
        /// or without further instructions.
        /// </summary>
        [XmlElement("message")]
        public string Message { get; set; }


        /// <summary>
        /// A list of method calls going all the way to the top level,
        /// which makes it easy to pinpoint the exact origin of the
        /// error but is not always available.
        /// </summary>
        [XmlElement("stackTrace")]
        public StackFrame[] StackTrace { get; set; }


        /// <summary>
        /// Converts a .NET exception to a DiXon-JSClientPages 
        /// page error
        /// </summary>
        /// <param name="exception">any .NET exception</param>
        /// <returns>a page error</returns>
        public static PageError FromException(Exception exception)
        {
            var exceptionStackTrace = new StackTrace(exception, true);
            return new PageError()
            {
                Source = PageErrorSource.Compiler,
                Message = exception.Message,
                StackTrace =
                (
                    from frame in exceptionStackTrace.GetFrames()
                    select new StackFrame()
                    {
                        Line = frame.GetFileLineNumber(),
                        File = frame.GetFileName(),
                        Method = frame.GetMethod().ToString()
                    }
                ).ToArray(),
            };
        }


        /// <summary>
        /// Serializes this page error to XML and outputs it to
        /// a text writer
        /// </summary>
        /// <param name="writer">the destination</param>
        public void WriteXML(TextWriter writer)
        {
            var settings = new XmlWriterSettings();
            settings.OmitXmlDeclaration = true;
            var xWriter = XmlWriter.Create(writer, settings);
            var xmlns = new XmlSerializerNamespaces();
            new XmlSerializer(typeof(PageError)).Serialize(xWriter, this, xmlns);
        }

        /// <summary>
        /// Serializes this page error to XML amd outputs it to
        /// a text writer
        /// </summary>
        /// <param name="writer">the destination</param>
        public void WriteXML(XmlWriter writer)
        {
            new XmlSerializer(typeof(PageError)).Serialize(writer, this);
        }


        /// <summary>
        /// Serializes this error as a JavaScript object and outputs
        /// it
        /// </summary>
        /// <param name="stream">the target string</param>
        /// <param name="encoding">the target encoding</param>
        public void WriteScript(Stream stream, Encoding encoding)
        {
            var streamWriter = new StreamWriter(stream, encoding);
            var scriptWriter =
                new ScriptWriter(streamWriter);
            scriptWriter.Write(this);
            streamWriter.Flush();
        }

        /// <summary>
        /// Converts this error to a JavaScript object and returns it
        /// as a string
        /// </summary>
        /// <returns>the JavaScript representation of this error</returns>
        public string ToScript()
        {

            var stream = new MemoryStream();
            WriteScript(stream, Encoding.UTF8);
            return stream.ToString();
        }

        /// <summary>
        /// Outputs this error as a JavaScript call to display it
        /// </summary>
        /// <param name="stream">the target stream</param>
        /// <param name="encoding">the target encoding</param>
        public void DisplayInScript(Stream stream, Encoding encoding)
        {
            var writer = new StreamWriter(stream, encoding);
            var scriptWriter =
                new ScriptWriter(writer);
            scriptWriter
                .StartCall(DisplayMethod)
                .Write(this)
                .EndCall();
            writer.Flush();
        }

        #region IScriptable Members


        /// <summary>
        /// Converts this error to a JavaScript object using the script
        /// writer provided
        /// </summary>
        /// <param name="writer">A DiXon script writer</param>
        /// <returns>the original script writer instance</returns>
        public void WriteScript(ScriptWriter writer)
        {

            writer
            .StartObject()
            .WriteKeyValuePair("message", Message)
            .EndItem()
            .WriteKeyValuePair("source", Source.ToString())
            .EndItem()
            .WriteKey("stackTrace")
            .WriteItems(StackTrace)
            .EndObject();

        }

        #endregion
    }
}
